﻿// <copyright file="AdRotator.cs" company="Microsoft Corporation">
// Copyright (c) 2008 All Right Reserved
// </copyright>
// <author>Michael S. Scherotter</author>
// <email>mischero@microsoft.com</email>
// <date>2009-01-07</date>
// <summary>Ad Rotator Silverlight2 control</summary>

using System.IO;

namespace Synergist
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Browser;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Markup;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Media.Imaging;
    using System.Windows.Shapes;
    using System.Windows.Threading;
    using System.Xml.Linq;

    /// <summary>
    /// Silverlight Ad Rotator
    /// </summary>
    [TemplateVisualState(Name = "Normal",  GroupName = "CommonStates")]
    [TemplateVisualState(Name = "ShowAd1", GroupName = "AdStates")]
    [TemplateVisualState(Name = "ShowAd2", GroupName = "AdStates")]
    public class AdRotator : Control, INotifyPropertyChanged
    {
        #region Fields

        private int index;

        /// <summary>
        /// format dictionary
        /// </summary>
        private static Dictionary<AdFormat, Size> formats = new Dictionary<AdFormat, Size>();

        /// <summary>
        /// the current ad format
        /// </summary>
        private AdFormat format;

        /// <summary>
        /// The ad file
        /// </summary>
        private Uri advertisementFile;

        /// <summary>
        /// the ad collection
        /// </summary>
        private IEnumerable<XElement> ads;

        /// <summary>
        /// the ad rotation timer
        /// </summary>
        private DispatcherTimer timer = new DispatcherTimer();

        /// <summary>
        /// the ad rotator is showing image 1
        /// </summary>
        private bool usingImage1 = true;

        private TextBlock formatName;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the AdRotator class.
        /// </summary>
        public AdRotator()
        {
            DefaultStyleKey = typeof(AdRotator);

            this.Format = AdFormat.Square;

            this.TimePerAd = new TimeSpan(0, 0, 10);

            this.Loaded += new RoutedEventHandler(this.OnLoaded);

            this.timer.Tick += new EventHandler(this.OnTimerTick);

            this.MouseEnter += new MouseEventHandler(this.OnMouseEnter);

            this.MouseLeave += new MouseEventHandler(this.OnMouseLeave);
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Property changed event handler
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the first image
        /// </summary>
        public Image Image1 { get; set; }

        /// <summary>
        /// Gets or sets the second image
        /// </summary>
        public Image Image2 { get; set; }

        /// <summary>
        /// Gets or sets the media for Ad1
        /// </summary>
        public MediaElement Media1 { get; set; }

        /// <summary>
        /// Gets or sets the media for Ad2
        /// </summary>
        public MediaElement Media2 { get; set; }

        /// <summary>
        /// Gets or sets the grid parent for Ad1
        /// </summary>
        public Grid Parent1 { get; set; }

        /// <summary>
        /// Gets or sets the grid parent for Ad2
        /// </summary>
        public Grid Parent2 { get; set; }

        /// <summary>
        /// Gets or sets the design time graphics
        /// </summary>
        public FrameworkElement DesignTime { get; set; }

        /// <summary>
        /// Gets or sets the ad format
        /// </summary>
        [Category("Ad")]
        [Description("LeaderBoard: 728x90\nBanner: 468x60\nHalf Banner: 234x60\nSkyscraper: 128x600\nWide Skyscraper: 160x600\nVertical Banner: 120x240\nLarge Rectangle: 336x280\nMedium Rectangle: 300x250\nSquare: 250x250\nSmall Square: 200x200\nSmall Rectangle: 180x150\nButton 125x125")]
        [DefaultValue(AdFormat.Square)]
        public AdFormat Format
        {
            get
            {
                return this.format;
            }

            set
            {
                if (formats.Count == 0)
                {
                    formats.Add(AdFormat.LeaderBoard, new Size(728, 90));
                    formats.Add(AdFormat.Banner, new Size(468, 60));
                    formats.Add(AdFormat.HalfBanner, new Size(234, 60));
                    formats.Add(AdFormat.Skyscraper, new Size(128, 600));
                    formats.Add(AdFormat.WideSkyscraper, new Size(160, 600));
                    formats.Add(AdFormat.VerticalBanner, new Size(120, 240));
                    formats.Add(AdFormat.LargeRectangle, new Size(336, 280));
                    formats.Add(AdFormat.MediumRectangle, new Size(300, 250));
                    formats.Add(AdFormat.Square, new Size(250, 250));
                    formats.Add(AdFormat.SmallSquare, new Size(200, 200));
                    formats.Add(AdFormat.SmallRectangle, new Size(180, 150));
                    formats.Add(AdFormat.Button, new Size(125, 125));
                }

                if (value != AdFormat.Custom)
                {
                    var size = formats[value];

                    this.Width = size.Width;
                    this.Height = size.Height;

                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("Width"));
                        this.PropertyChanged(this, new PropertyChangedEventArgs("Height"));
                    }
                }

                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs("AdFormat"));
                }

                this.format = value;

                UpdateFormatName();
            }
        }

        private void UpdateFormatName()
        {
            if (this.formatName != null)
            {
                this.formatName.Text = string.Format(System.Globalization.CultureInfo.CurrentCulture, "{0}\n{1}x{2}", this.format.ToString(), this.Width, this.Height);
            }
        }

        /// <summary>
        /// Gets or sets the current ad unit
        /// </summary>
        [Category("Ad")]
        public Ad AdUnit { get; set; }

        /// <summary>
        /// Gets or sets the hyperlink button
        /// </summary>
        public HyperlinkButton Button { get; set; }

        /// <summary>
        /// Gets or sets the image container
        /// </summary>
        public FrameworkElement ImageContainer { get; set; }

        /// <summary>
        /// Gets or sets the time to show each ad
        /// </summary>
        [Category("Ad")]
        [Description("The time to show each ad.")]
        public TimeSpan TimePerAd
        { 
            get
            {
                return this.timer.Interval;
            }

            set
            {
                this.timer.Interval = value;
            }
        }

        /// <summary>
        /// Gets or sets the Advertisement File
        /// </summary>
        [Category("Ad")]
        [Description("the AdRotator XML file that lists all of the ads")]
        [TypeConverter(typeof(UriTypeConverter))]
        public Uri Source
        {
            get
            {
                return this.advertisementFile;
            }

            set
            {
                this.advertisementFile = value;

                try
                {
                    this.DownloadAdFile();
                }
                catch (System.Exception se)
                {
                    try
                    {
                        using (StreamReader sr = new StreamReader(Application.GetResourceStream(value).Stream))
                        {
                            Parse(sr.ReadToEnd());
                        }

                    }
                    catch (Exception)
                    {
                        
                    }
                    
                }
            }
        }

        /// <summary>
        /// Gets or sets the Keyword filter
        /// </summary>
        [Category("Ad")]
        [Description("the Keyword filter to apply to the advertisement file")]
        public string KeywordFilter { get; set; }

        /// <summary>
        /// Gets or sets the Link target
        /// </summary>
        [Category("Ad")]
        [DefaultValue("_blank")]
        [Description("the hyperlink target to show the ads when clicked.")]
        public string Target
        {
            get
            {
                return this.Button.TargetName;
            }

            set
            {
                this.Button.TargetName = value;
            }
        }

        #endregion

        /// <summary>
        /// Override template application to get controls
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.Button = GetTemplateChild("Button") as HyperlinkButton;
            this.DesignTime = GetTemplateChild("DesignTime") as FrameworkElement;
            this.Image1 = GetTemplateChild("Image1") as Image;
            this.Image2 = GetTemplateChild("Image2") as Image;
            this.Media1 = GetTemplateChild("Media1") as MediaElement;
            this.Media2 = GetTemplateChild("Media2") as MediaElement;
            this.Parent1 = GetTemplateChild("Parent1") as Grid;
            this.Parent2 = GetTemplateChild("Parent2") as Grid;
            this.formatName = GetTemplateChild("FormatName") as TextBlock;

            this.Button.IsTabStop = false;
            this.Button.Click += new RoutedEventHandler(this.OnButtonClick);

            if (this.Media1 == null)
            {
                System.Diagnostics.Debug.WriteLine("Control template does not have a MediaElement named Media1.  Control cannot show Media ads.");
            }
            else
            {
                this.Media1.MediaOpened += new RoutedEventHandler(this.Media1_MediaOpened);
            }

            UpdateFormatName();

            if (HtmlPage.IsEnabled)//DesignerProperties.GetIsInDesignMode(this) && this.DesignTime != null)
            {
                this.DesignTime.Visibility = Visibility.Collapsed;
            }

            if (this.Media2 == null)
            {
                System.Diagnostics.Debug.WriteLine("Control template does not have a MediaElement named Media2.  Control cannot show Media ads.");
            }
            else
            {
                this.Media2.MediaOpened += new RoutedEventHandler(this.Media2_MediaOpened);
            }
        }

        #region Implementation

        /// <summary>
        /// Gets the impression weighting
        /// </summary>
        /// <param name="ad">the ad to examine</param>
        /// <returns>a decimal value indicating the impression weighting</returns>
        private static decimal Impressions(XElement ad)
        {
            var impressions = ad.Element("Impressions");

            if (impressions != null)
            {
                return System.Convert.ToDecimal(impressions.Value, System.Globalization.CultureInfo.InvariantCulture);
            }

            return 1;
        }

        /// <summary>
        /// Media 2 was opened
        /// </summary>
        /// <param name="sender">the media element</param>
        /// <param name="e">the routed event argument</param>
        private void Media2_MediaOpened(object sender, RoutedEventArgs e)
        {
            this.Button.NavigateUri = this.AdUnit.NavigateUrl;
            this.Button.SetValue(ToolTipService.ToolTipProperty, this.AdUnit.AlternateText);

            if (this.usingImage1)
            {
                VisualStateManager.GoToState(this, "ShowAd2", true);
            }
            else
            {
                VisualStateManager.GoToState(this, "ShowAd1", true);
            }

            if (this.Media1 != null)
            {
                this.Media1.Stop();
            }

            this.Media2.Play();

            this.usingImage1 = !this.usingImage1;
        }

        /// <summary>
        /// Media 1 was opened
        /// </summary>
        /// <param name="sender">the media element</param>
        /// <param name="e">the routed event argument</param>
        private void Media1_MediaOpened(object sender, RoutedEventArgs e)
        {
            this.Button.NavigateUri = this.AdUnit.NavigateUrl;
            this.Button.SetValue(ToolTipService.ToolTipProperty, this.AdUnit.AlternateText);

            if (this.usingImage1)
            {
                VisualStateManager.GoToState(this, "ShowAd2", true);
            }
            else
            {
                VisualStateManager.GoToState(this, "ShowAd1", true);
            }

            if (this.Media2 != null)
            {
                this.Media2.Stop();
            }

            this.Media1.Play();

            this.usingImage1 = !this.usingImage1;
        }

        /// <summary>
        /// Download the ad file
        /// </summary>
        private void DownloadAdFile()
        {
            WebClient client = new WebClient();

            client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(this.OnAdFileDownloaded);

            client.DownloadStringAsync(this.advertisementFile);
        }

        /// <summary>
        /// Ad file has completed downloading
        /// </summary>
        /// <param name="sender">the web client</param>
        /// <param name="e">the download string completed event arguments</param>
        private void OnAdFileDownloaded(object sender, DownloadStringCompletedEventArgs e)
        {
            try
            {
                if (e.Error != null)
                {
                    throw e.Error;
                }
                else
                {
                    Parse(e.Result);
                }
            }
            catch (Exception)
            {
                try
                {
                    using (StreamReader sr = new StreamReader(Application.GetResourceStream(new Uri("/gamenesembedd;component/ads.xml", UriKind.Relative)).Stream))
                    {
                        Parse(sr.ReadToEnd());
                    }
                }
                catch (Exception)
                {

                }
            }

            
        }

        private void Parse(string text)
        {
            XDocument doc = XDocument.Parse(text);

            this.ads = doc.Descendants("Ad");

            this.SelectAd();
        }

        /// <summary>
        /// Select the next ad
        /// </summary>
        private void SelectAd()
        {
            if (this.ads == null)
            {
                return;
            }

            IEnumerable<XElement> adsToUse = null;
            if (string.IsNullOrEmpty(this.KeywordFilter))
            {
                adsToUse = this.ads;
            }
            else
            {
                adsToUse = from eachAd in this.ads
                           where eachAd.Element("Keyword") != null && eachAd.Element("Keyword").Value == this.KeywordFilter
                           select eachAd;
            }

            XElement selectedAd = adsToUse.ElementAt(this.index);

            this.index++;

            if (this.index >= adsToUse.Count())
            {
                this.index = 0;
            }

            //XElement selectedAd = SelectRandomAd(adsToUse);

            var height = selectedAd.Element("Height");
            var width = selectedAd.Element("Width");
            var navigateUrl = selectedAd.Element("NavigateUrl");
            var altText = selectedAd.Element("AlternateText");
            var keyword = selectedAd.Element("Keyword");
            var impressions = selectedAd.Element("Impressions");

            this.AdUnit = new Ad()
            {
                ImageUrl = new Uri(selectedAd.Element("ImageUrl").Value, UriKind.RelativeOrAbsolute),
                Height = height == null ? 0.0 : Convert.ToDouble(height.Value, System.Globalization.CultureInfo.InvariantCulture),
                Width = width == null ? 0.0 : Convert.ToDouble(width.Value, System.Globalization.CultureInfo.InvariantCulture),
                NavigateUrl = navigateUrl == null ? null : new Uri(navigateUrl.Value, UriKind.Absolute),
                AlternateText = altText == null ? null : altText.Value,
                Keyword = keyword == null ? null : keyword.Value,
                Impressions = impressions == null ? 1 : Convert.ToInt32(impressions.Value, System.Globalization.CultureInfo.InvariantCulture)
            };

            Button.NavigateUri = AdUnit.NavigateUrl;
            string path = this.AdUnit.ImageUrl.ToString();

            var extension = System.IO.Path.GetExtension(path).ToUpper(System.Globalization.CultureInfo.InvariantCulture);

            if (extension == ".PNG" || extension == ".JPG" || extension == ".JPEG")
            {
                SelectImage();
            }
            else if (extension == ".WMV")
            {
                SelectMedia();
            }
            else if (extension == ".XAML")
            {
                SelectXaml();
            }
            else
            {
                System.Diagnostics.Debug.WriteLine("Unknown extension ({0}) for Ad {1}", extension, path);
            }
        }

        private static XElement SelectRandomAd(IEnumerable<XElement> adsToUse)
        {
            var total = adsToUse.Sum(new Func<XElement, decimal>(Impressions));

            Random random = new Random();

            int index = random.Next((int)total);

            XElement selectedAd = null;

            foreach (var ad in adsToUse)
            {
                index = index - (int)Impressions(ad);

                if (index < 0)
                {
                    selectedAd = ad;

                    break;
                }
            }
            return selectedAd;
        }

        /// <summary>
        /// Select an image
        /// </summary>
        private void SelectImage()
        {
            var bitmap = new BitmapImage(this.AdUnit.ImageUrl);

            bitmap.DownloadProgress += new EventHandler<DownloadProgressEventArgs>(this.OnBitmapDownloadProgress);

            if (this.usingImage1)
            {
                this.Image2.Visibility = Visibility.Visible;
                this.Image2.Source = bitmap;
                if (this.Media2 != null)
                {
                    this.Media2.Visibility = Visibility.Collapsed;
                }

                this.Parent2.Visibility = Visibility.Collapsed;
            }
            else
            {
                this.Image1.Visibility = Visibility.Visible;
                this.Image1.Source = bitmap;
                if (this.Media1 != null)
                {
                    this.Media1.Visibility = Visibility.Collapsed;
                }

                this.Parent1.Visibility = Visibility.Collapsed;
            }
        }

        /// <summary>
        /// Select XAML
        /// </summary>
        private void SelectXaml()
        {
            if (this.usingImage1)
            {
                this.Parent2.Visibility = Visibility.Visible;

                if (this.Media2 != null)
                {
                    this.Media2.Visibility = Visibility.Collapsed;
                }

                this.Image2.Visibility = Visibility.Collapsed;
            }
            else
            {
                this.Parent1.Visibility = Visibility.Visible;

                if (this.Media1 != null)
                {
                    this.Media1.Visibility = Visibility.Collapsed;
                }

                this.Image1.Visibility = Visibility.Collapsed;
            }

            try
            {
                new Uri(AdUnit.ImageUrl.OriginalString, UriKind.Absolute);
                var client = new WebClient();
                client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(this.OnXamlDownloaded);
                client.DownloadStringAsync(this.AdUnit.ImageUrl);
            }
            catch (Exception)
            {
               //Get local
                using (StreamReader sr = new StreamReader(Application.GetResourceStream(new Uri(AdUnit.ImageUrl.OriginalString, UriKind.Relative)).Stream))
                {
                    LoadXaml(sr.ReadToEnd());    
                }
                
            }
        }

        /// <summary>
        /// Select media
        /// </summary>
        private void SelectMedia()
        {
            if (this.usingImage1)
            {
                if (this.Media2 != null)
                {
                    this.Media2.Visibility = Visibility.Visible;
                    this.Media2.Source = this.AdUnit.ImageUrl;
                    this.Media2.Source = this.AdUnit.ImageUrl;
                }

                this.Image2.Visibility = Visibility.Collapsed;

                this.Parent2.Visibility = Visibility.Collapsed;
            }
            else
            {
                if (this.Media1 != null)
                {
                    this.Media1.Visibility = Visibility.Visible;
                    this.Media1.Source = this.AdUnit.ImageUrl;
                    this.Media1.Source = this.AdUnit.ImageUrl;
                }

                this.Image1.Visibility = Visibility.Collapsed;
                this.Parent1.Visibility = Visibility.Collapsed;
            }
        }

        /// <summary>
        /// Event handler when xaml was downloaded
        /// </summary>
        /// <param name="sender">the WebClient</param>
        /// <param name="e">download string completed event arguments</param>
        private void OnXamlDownloaded(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                System.Diagnostics.Debug.WriteLine(e.Error.Message);

                return;
            }

            LoadXaml(e.Result);
        }

        private void LoadXaml(string xaml)
        {
            var newElement = XamlReader.Load(xaml) as UIElement;

            this.usingImage1 = !this.usingImage1;

            if (this.usingImage1)
            {
                this.Parent1.Children.Clear();

                this.Parent1.Children.Add(newElement);

                if (this.Media2 != null)
                {
                    this.Media2.Stop();
                }

                VisualStateManager.GoToState(this, "ShowAd1", true);
            }
            else
            {
                this.Parent2.Children.Clear();

                this.Parent2.Children.Add(newElement);

                if (this.Media1 != null)
                {
                    this.Media1.Stop();
                }

                VisualStateManager.GoToState(this, "ShowAd2", true);
            }
        }

        /// <summary>
        /// Bitmap image download progress
        /// </summary>
        /// <param name="sender">the bitmap</param>
        /// <param name="e">the download progress event arguments</param>
        private void OnBitmapDownloadProgress(object sender, DownloadProgressEventArgs e)
        {
            this.Button.NavigateUri = this.AdUnit.NavigateUrl;
            this.Button.SetValue(ToolTipService.ToolTipProperty, this.AdUnit.AlternateText);

            if (this.usingImage1)
            {
                VisualStateManager.GoToState(this, "ShowAd2", true);

                if (this.Media1 != null)
                {
                    this.Media1.Stop();
                }
            }
            else
            {
                VisualStateManager.GoToState(this, "ShowAd1", true);

                if (this.Media2 != null)
                {
                    this.Media2.Stop();
                }
            }

            this.usingImage1 = !this.usingImage1;
        }

        /// <summary>
        /// Mouse enters the control so show the navigate URL in the status bar
        /// </summary>
        /// <param name="sender">the user control</param>
        /// <param name="e">the mouse event arguments</param>
        private void OnMouseEnter(object sender, MouseEventArgs e)
        {
            if (HtmlPage.IsEnabled && this.Button.NavigateUri != null)
            {
                HtmlPage.Window.SetProperty("status", this.Button.NavigateUri.ToString());
            }
        }

        /// <summary>
        /// Mouse leaves the control so hide the navigate URL in the status bar
        /// </summary>
        /// <param name="sender">the user control</param>
        /// <param name="e">the mouse event arguments</param>
        private void OnMouseLeave(object sender, MouseEventArgs e)
        {
            if (HtmlPage.IsEnabled && this.Button.NavigateUri != null)
            {
                HtmlPage.Window.SetProperty("status", string.Empty);
            }
        }

        /// <summary>
        /// Timer ticked
        /// </summary>
        /// <param name="sender">the timer object</param>
        /// <param name="e">the event arguments</param>
        private void OnTimerTick(object sender, EventArgs e)
        {
            this.SelectAd();
        }

        /// <summary>
        /// Ad clicked
        /// </summary>
        /// <param name="sender">the ad button</param>
        /// <param name="e">the routed event arguments</param>
        private void OnButtonClick(object sender, RoutedEventArgs e)
        {
            var url = HtmlPage.Document.DocumentUri.ToString() + "/ClickedAd/" + this.AdUnit.NavigateUrl.ToString();

            var analytics = HtmlPage.Window.Eval("msAnalytics") as ScriptObject;

            if (analytics == null)
            {
                return;
            }

            analytics.SetProperty("PageViewRegistered", false);
            analytics.SetProperty("TargetPage", url);
            analytics.Invoke("TrackPage", null);
        }

        /// <summary>
        /// Start the timer once the control has loaded
        /// </summary>
        /// <param name="sender">the ad rotator</param>
        /// <param name="e">the routed event argumetns</param>
        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            this.timer.Start();
        }

        #endregion
    }
}
